/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.commons.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import sk.jp.commons.exception.I18nSupportable;
import sk.jp.commons.exception.ApplicationException;
import sk.jp.commons.exception.ApplicationCheckedException;
import sk.jp.commons.tools.Messages;

/**
 * Utilities for exception manipulation.
 * 
 * @author Jan Pastor 
 */
public class ExceptionUtils {

    private ExceptionUtils() {
    }

    // ----------------------------------------------------------------------

    /**
     * Returns exception class name.
     * @param t
     * @return exception class name.
     */
    public static String getClass(Throwable t) {
        return t.getClass().getName();
    }

    /**
     * Returns full exception stacktrace (with causes) in string.
     * @param t
     * @return exception stacktrace
     */
    public static String getStacktrace(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw, true));
        return sw.toString();
    }

    /**
     * Returns exception stacktrace as array of stacktrace elements in string
     * representation.
     * <p>
     * First element in array (index 0) is the top of stacktrace - last invoked
     * method.
     * @param t
     * @return exception stacktrace as array of stacktrace elements in string
     *         representation.
     */
    public static String[] getStacktraceArray(Throwable t) {
        StackTraceElement[] ste = t.getStackTrace();
        String[] out = new String[ste.length];
        for (int i = 0; i < ste.length; i++) {
            out[i] = ste[i].toString();
        }
        return out;
    }

    /**
     * Returns full stacktrace (with causes) as HTML fragment.
     * <p>
     * This method is designed for HTML output.
     * @param t
     * @return stacktrace as string, where elements ends with "&lt;br&gt;\n".
     */
    public static String getStacktraceBr(Throwable t) {
        return StringUtils.toHtmlString(getStacktrace(t));
    }

    /**
     * Returns root cause of given exception.
     * @param t
     * @return root cause or null if given exception is null.
     */
    public static Throwable getRootCause(Throwable t) {
        if (t == null) {
            return t;
        }
        while (t.getCause() != null) {
            t = t.getCause();
        }
        return t;
    }

    /**
     * Translate exception message code to localized message, with included
     * exception message parameters.
     * <p>
     * <u>Translation specification:</u><br>
     * For all elements in exception message params, in exception message
     * (obtained from <code>messagesMap</code> by exception message code) are
     * replaced tokens '<i>{i}</i>' with i-th param value.<br>
     * <b>For example:</b> If exception message code is '<i>app.err.badname</i>',
     * and <code>messagesMap</code> contains key: '<i>app.err.badname</i>'
     * and value: '<i>Invalid value for name ({0}) and phone number ({1})</i>',
     * and exception message params contains {('name', 'Juro'), ('phone_number',
     * '6856')}, result of translation is: '<i>Invalid value for name (Juro)
     * and phone number (6856)</i>'.
     * @param messagesMap map of localized messages (key: message code, value:
     *            localized message). If this map doesn't contains exception
     *            message code, this method returns exception message.
     * @param applicationException exception to translate.
     * @return translated exception message. If given <code>messagesMap</code>
     *         doesn't contains exception message code, returns exception
     *         message.
     */
    public static String translateMessage(Map messagesMap, I18nSupportable applicationException) {
        if (messagesMap == null)
            throw new NullPointerException("Parameter messagesMap is null");
        if (applicationException == null)
            throw new NullPointerException("Parameter exception is null");
        //
        String localizedMsg = (String) messagesMap.get(applicationException.getCode());
        return translateMessage(localizedMsg, applicationException);
    }

    /**
     * Translate exception message code to localized message, with included
     * exception message parameters.
     * <p>
     * <u>Translation specification:</u><br>
     * For all elements in exception message params, in
     * <code>localizedRawMessage</code> are replaced tokens '<i>{i}</i>'
     * with i-th param value.<br>
     * <b>For example:</b> If <code>localizedRawMessage</code> is: '<i>Invalid
     * value for name ({0}) and phone number ({1})</i>', and exception message
     * params contains {('name', 'Juro'), ('phone_number', '6856')}, result of
     * translation is: '<i>Invalid value for name (Juro) and phone number
     * (6856)</i>'.
     * @param localizedRawMessage raw localized exception message, with tokens
     *            as parameters values. If null, exception message returns.
     * @param applicationException exception to translate.
     * @return translated exception message. If given
     *         <code>localizedRawMessage</code> is null, returns exception
     *         message.
     */
    public static String translateMessage(String localizedRawMessage,
            I18nSupportable applicationException) {
        if (localizedRawMessage == null) {
            if (applicationException instanceof Throwable) {
                return ((Throwable) applicationException).getMessage();
            }
        }
        Collection params = applicationException.getParams().values();
        int i = 0;
        for (Iterator iterator = params.iterator(); iterator.hasNext();) {
            String paramValue = (String) iterator.next();
            localizedRawMessage = StringUtils.replaceAll(localizedRawMessage, "{" + i + "}",
                    paramValue);
            i++;
        }
        return localizedRawMessage;
    }

    /**
     * Translate exception message to localized message, with included exception
     * message parameters.
     * <p>
     * If given exception implements {@link I18nSupportable} interface, uses
     * {@link Messages} to get localized message (must be initialized before!).
     * If not, or if exception has not localized message in catalog, returns
     * result of <code>toString()</code> method.
     * @param throwable exception to translate.
     * @return translated exception message or result of <code>toString()</code>
     *         if can't be translated.
     */
    public static String translateException(Throwable throwable) {
        if (throwable instanceof I18nSupportable) {
            I18nSupportable i18nExc = (I18nSupportable) throwable;
            if (i18nExc.getCode() == null) {
                // no code
                return throwable.toString();
            }
            String msg;
            msg = Messages.getMessageOrNull(i18nExc.getCode(),
                    i18nExc.getParams().values().toArray());
            if (msg == null) {
                // missing in messages catalog
                return throwable.toString();
            }
            return msg;
        } else {
            return throwable.toString();
        }
    }

    /**
     * Convenience method for build map of parameters, used by
     * {@link ApplicationException} or {@link ApplicationCheckedException}. See
     * description of {@link CollectionUtils#paramsToMap(Object[])}.
     * <p>
     * Creates map of exception parameters from given variable list of parameter
     * names and values.
     * <p>
     * Values are transformed to string using <code>String.valueOf</code>
     * method.
     * @param params a list of parameter names and values (paramName0,
     *            paramValue0, paramName1, paramValue1, ...). Number of list
     *            items must be even (param name + param value). If null or
     *            empty, returns empty map.
     * @return ordered map of parameters (key: parameter name, value: parameter
     *         value).
     */
    public static LinkedHashMap<String, Object> paramsToMap(Object... params) {
        // noinspection unchecked
        return (LinkedHashMap<String, Object>) CollectionUtils.paramsToMap(params);
    }

}
